BOLT 2 - channel management protocol
2022-04-10 ยท 14 min read
Source: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md
This BOLT describes the bilateral messaging protocol for (1) establishing, (2) operating, and (3) closing lightning payment channels between two LN peers.
def: channel_id: [u8; 32]
#
Channels are identified by a channel_id
, which is derived from the funding tx id and index.
Before the channel is funded, the channel_id
is random.
Pre-funded channels are not yet uniquely identified. Use (src_node_id, dest_node_id, channel_id)
for identification.
Parties can also offer "short channel id" (SCID) aliases (size = u64
), which we can use instead of the actual channel_id
to avoid leaking the on-chain funding UTXO.
Channel Establishment #
Channels may be established after a secure connection is authenticated and features negotiated.
The establishment process goes through 3 phases: (1-2) negotiate the channel parameters, (3-4) create the funding tx, (5-6) lock in the channel once the funding tx is confirmed on-chain.
funder fundee
+-------+ +-------+
| |--(1)--- open_channel ----->| | <
| |<-(2)-- accept_channel -----| | <
| | | | < temporary channel id
| A |--(3)-- funding_created --->| B | <
| |<-(4)-- funding_signed -----| | <
| | | | ---
| |--(5)--- funding_locked ---->| | <
| |<-(6)--- funding_locked -----| | < real channel id
+-------+ +-------+ <
def: open_channel
wire message #
The channel funder sends an open_channel
message to the channel fundee. Here, funder and fundee are negotiating the channel parameters for their new channel contract. A few of the important channel parameters:
- the channel size (amount to be funded or max liquidity)
- optional initial payment to fundee
- channel fee rate
- supported channel features
- bounds: minimum HTLC size, dust limits, max num. HTLCs, max in-flight value
- on-chain confirmations-to-finality
The channel id is just a random nonce at this point.
See: msgs::OpenChannel
/// An open_channel message to be sent or received from a peer
pub struct OpenChannel {
/// The genesis hash of the blockchain where the channel is to be opened
pub chain_hash: BlockHash,
/// A temporary channel ID, until the funding outpoint is announced
pub temporary_channel_id: [u8; 32],
/// The channel value
pub funding_satoshis: u64,
/// The amount to push to the counterparty as part of the open, in
/// milli-satoshi
pub push_msat: u64,
/// The threshold below which outputs on transactions broadcast by
/// sender will be omitted
pub dust_limit_satoshis: u64,
/// The maximum inbound HTLC value in flight towards sender, in
/// milli-satoshi
pub max_htlc_value_in_flight_msat: u64,
/// The minimum value unencumbered by HTLCs for the counterparty
/// to keep in the channel
pub channel_reserve_satoshis: u64,
/// The minimum HTLC size incoming to sender, in milli-satoshi
pub htlc_minimum_msat: u64,
/// The feerate per 1000-weight of sender generated transactions,
/// until updated by update_fee
pub feerate_per_kw: u32,
/// The number of blocks which the counterparty will have to wait
/// to claim on-chain funds if they broadcast a commitment transaction
pub to_self_delay: u16,
/// The maximum number of inbound HTLCs towards sender
pub max_accepted_htlcs: u16,
/// The sender's key controlling the funding transaction
pub funding_pubkey: PublicKey,
/// Used to derive a revocation key for transactions broadcast
/// by counterparty
pub revocation_basepoint: PublicKey,
/// A payment key to sender for transactions broadcast by counterparty
pub payment_point: PublicKey,
/// Used to derive a payment key to sender for transactions
/// broadcast by sender
pub delayed_payment_basepoint: PublicKey,
/// Used to derive an HTLC payment key to sender
pub htlc_basepoint: PublicKey,
/// The first to-be-broadcast-by-sender transaction's per
/// commitment point
pub first_per_commitment_point: PublicKey,
/// Channel flags
pub channel_flags: u8,
/// Optionally, a request to pre-set the to-sender output's
/// scriptPubkey for when we collaboratively close
pub shutdown_scriptpubkey: OptionalField<Script>,
/// The channel type that this channel will represent. If none is
/// set, we derive the channel type from the intersection of our
/// feature bits with our counterparty's feature bits from the
/// Init message.
pub channel_type: Option<ChannelTypeFeatures>,
}
funder flow #
ChannelManager::create_channel
fundee flow #
ChannelManager::internal_open_channel
Channel::accept_inbound_channel
(If configured for user-level authorization) Generate Event::OpenChannelRequest
def: accept_channel
wire message #
The fundee can choose to accept a new channel proposal by responding with an accept_channel
message.
The channel id is still the same random nonce.
/// An accept_channel message to be sent or received from a peer
pub struct AcceptChannel {
/// A temporary channel ID, until the funding outpoint is announced
pub temporary_channel_id: [u8; 32],
/// The threshold below which outputs on transactions broadcast
/// by sender will be omitted
pub dust_limit_satoshis: u64,
/// The maximum inbound HTLC value in flight towards sender,
/// in milli-satoshi
pub max_htlc_value_in_flight_msat: u64,
/// The minimum value unencumbered by HTLCs for the counterparty
/// to keep in the channel
pub channel_reserve_satoshis: u64,
/// The minimum HTLC size incoming to sender, in milli-satoshi
pub htlc_minimum_msat: u64,
/// Minimum depth of the funding transaction before the channel
/// is considered open
pub minimum_depth: u32,
/// The number of blocks which the counterparty will have to wait
/// to claim on-chain funds if they broadcast a commitment transaction
pub to_self_delay: u16,
/// The maximum number of inbound HTLCs towards sender
pub max_accepted_htlcs: u16,
/// The sender's key controlling the funding transaction
pub funding_pubkey: PublicKey,
/// Used to derive a revocation key for transactions broadcast
/// by counterparty
pub revocation_basepoint: PublicKey,
/// A payment key to sender for transactions broadcast by counterparty
pub payment_point: PublicKey,
/// Used to derive a payment key to sender for transactions
/// broadcast by sender
pub delayed_payment_basepoint: PublicKey,
/// Used to derive an HTLC payment key to sender for transactions
/// broadcast by counterparty
pub htlc_basepoint: PublicKey,
/// The first to-be-broadcast-by-sender transaction's per
/// commitment point
pub first_per_commitment_point: PublicKey,
/// Optionally, a request to pre-set the to-sender output's
/// scriptPubkey for when we collaboratively close
pub shutdown_scriptpubkey: OptionalField<Script>,
/// The channel type that this channel will represent. If none is
/// set, we derive the channel type from the intersection of our
/// feature bits with our counterparty's feature bits from the
/// Init message.
///
/// This is required to match the equivalent field in
/// [`OpenChannel::channel_type`].
pub channel_type: Option<ChannelTypeFeatures>,
}
fundee flow #
If accept: Channel::generate_accept_channel_message
funder flow #
ChannelManager::internal_accept_channel
Generate Event::FundingGenerationReady
def: funding_created
wire message #
The funder crafts a funding transaction, signs it, and sends it to the fundee for their signature.
The channel id is still a random nonce.
/// A funding_created message to be sent or received from a peer
pub struct FundingCreated {
/// A temporary channel ID, until the funding is established
pub temporary_channel_id: [u8; 32],
/// The funding transaction ID
pub funding_txid: Txid,
/// The specific output index funding this channel
pub funding_output_index: u16,
/// The signature of the channel initiator (funder) on the
/// initial commitment transaction
pub signature: Signature,
}
funder flow #
ChannelManager::funding_transaction_generated
ChannelManager::funding_transaction_generated_intern
Updates the channel state and creates the FundingCreated
p2p msg: Channel::get_outbound_funding_created
fundee flow #
ChannelManager::internal_funding_created
Fundee verifies, signs, returns FundingSigned
msg: Channel::funding_created
Fundee finally signs here: Channel::funding_created_signature
Fundee adds channel monitor hook to watch for confirmations. Responds with FundingSigned
message.
def: funding_signed
wire message #
The fundee responds with their signature on the funding tx. With this signature, the funding tx is now ready for propagation by the funder.
This is the first message where we refer to the channel by its real channel id and not the random nonce.
/// A funding_signed message to be sent or received from a peer
pub struct FundingSigned {
/// The channel ID
pub channel_id: [u8; 32],
/// The signature of the channel acceptor (fundee) on the
/// initial commitment transaction
pub signature: Signature,
}
fundee flow #
Fundee responds with FundingSigned
message in tail-end of Channel::funding_created
funder flow #
Verify, hook channel monitor on tx, and broadcast funding tx: ChannelManager::internal_funding_signed
Verify, build funding tx & tx monitor: Channel::funding_signed
Broadcast funding tx: chaininterface::BroadcasterInterface::broadcast_transaction
def: funding_locked
wire message #
Each node watches the chain, waiting for the funding tx to confirm. Once a node sees the funding tx confirm, they send a msgs::FundingLocked
message to the other peer.
Only once both peers have seen and verified that the funding tx has confirmed, and both peers have received each other's FundingLocked
message, can the channel finally enter normal operating mode.
The peers use the real channel id here.
/// A funding_locked message to be sent or received from a peer
pub struct FundingLocked {
/// The channel ID
pub channel_id: [u8; 32],
/// The per-commitment point of the second commitment transaction
pub next_per_commitment_point: PublicKey,
/// If set, provides a short_channel_id alias for this channel.
/// The sender will accept payments to be forwarded over this
/// SCID and forward them to this messages' recipient.
pub short_channel_id_alias: Option<u64>,
}
funder & fundee flow #
On funding tx confirmation #
The ChannelManager
on both sides will get poked when the funding tx is sufficiently confirmed. There appear to be several different possible interfaces, depending on how the chain listening is setup, like whether we're connected to a full node or light node, or if we have some block filtering etc...
The two I can see right off the bat are the traits chain::Listen
(whole new blocks) and chain::Confirm
(only specific txs we're interested in, only block headers).
You choose chain::Listen
if you're connected to a full node and chain::Confirm
if you're connected to a light node (my speculation).
These interfaces are impl'd by ChannelManager
, who will eventually call ChannelManager::do_chain_event
with a callback, which can possibly generate a FundingLocked
msg for sending to the other peer.
Each channel handles the tx confirm update in Channel::transactions_confirmed
. Check if the tx is relevant and sufficiently confirmed, then generate the FundingLocked
msg in Channel::check_get_funding_locked
.
If the Channel::is_usable
(both sides confirmed), then also Channel::get_announcement_sigs
, which generates a msgs::AccountmentSignatures
for sending, announcing the new channel.
Once the channel is_usable
, we enter Normal Channel Operation.
On remote funding_locked
msg rx #
Rx peer's funding_locked
msg: ChannelManager::internal_funding_locked
Update the channel state w/ Channel::funding_locked
.
Like before, if the channel is now usable (both sides confirmed), is configured for announcement, and is announcable, then generate, sign, and send the msg::AnnouncementSignatures
.
(not clear to me yet when these are announced)
Once the channel is_usable
, we enter Normal Channel Operation.
Cooperative Channel Close #
Both nodes can agree to graceful close a channel. This process consumes less fees and lets both sides access their funds immediately.
The other alternatives are unilateral close (maybe someone crashed) and revoked transaction close (the counterparty deliberately cheats and broadcasts an old state).
One side begins active shutdown
of the channel and indicates how/where they want to be paid. The passive shutdown
side responds similarly. Once a side has observed a shutdown
, they will no longer accept new HTLCs on this channel. Once the shutdown
is begun, both sides will wait for all pending HTLCs to resolve pending HTLCs to fail backwards. Once all HTLCs have resolved, both sides negotiate a final fee amount and broadcast the channel closure tx.
Protocol Diagram
+-------+ +-------+
| |--(1)----- shutdown ------->| |
| |<-(2)----- shutdown --------| |
| | | |
| | <complete all pending HTLCs> | |
| A | ... | B |
| | | |
| |--(3)-- closing_signed F1--->| |
| |<-(4)-- closing_signed F2----| |
| | ... | |
| |--(?)-- closing_signed Fn--->| |
| |<-(?)-- closing_signed Fn----| |
+-------+ +-------+
def: shutdown
wire message #
/// A shutdown message to be sent or received from a peer
pub struct Shutdown {
/// The channel ID
pub channel_id: [u8; 32],
/// The destination of this peer's funds on closing.
/// Must be in one of these forms: p2pkh, p2sh, p2wpkh, p2wsh.
pub scriptpubkey: Script,
}
active closer #
ChannelManager::close_channel
which immediately calls ChannelManager::close_channel_internal
. chanmgr grabs the channel, moves it into shutdown mode, maybe updates monitor w/ new shutdown script, sends shutdown msg to peer. Then fails all pending HTLCs backwards: ChannelManager::fail_htlc_backwards
. I guess we don't wait for HTLCs to resolve, just notify that they should fail.
Begin channel shutdown, craft Shutdown
message, return failed HTLCs: Channel::get_shutdown
passive closer #
Notify the channel about the remote shutdown
. Maybe update the monitor w/ shutdown script. Send the shutdown
response msg. Fail any pending HTLCs. ChannelManager::internal_shutdown
Channel rx'd shutdown
msg from remote peer; maybe generate a shutdown
response msg: Channel::shutdown
def: closing_signed
wire message #
The channel is officially shutdown and all pending HTLCs are resolved or failed. Now both peers must negotiate a fee for the closing transaction.
There are two negotiation methods: (1) the "old" method, where nodes go back and forth proposing a "fair" fee and either accepting or rejecting with their new fee, and (2) the "modern" method, where the funder proposes a permissible fee range, and the fundee chooses a value from the range.
/// A closing_signed message to be sent or received from a peer
pub struct ClosingSigned {
/// The channel ID
pub channel_id: [u8; 32],
/// The proposed total fee for the closing transaction
pub fee_satoshis: u64,
/// A signature on the closing transaction
pub signature: Signature,
/// The minimum and maximum fees which the sender is willing
/// to accept, provided only by new nodes.
pub fee_range: Option<ClosingSignedFeeRange>,
}
/// The minimum and maximum fees which the sender is willing to place
/// on the closing transaction. This is provided in [`ClosingSigned`]
/// by both sides to indicate the fee range they are willing to use.
pub struct ClosingSignedFeeRange {
/// The minimum absolute fee, in satoshis, which the sender is
/// willing to place on the closing transaction.
pub min_fee_satoshis: u64,
/// The maximum absolute fee, in satoshis, which the sender is
/// willing to place on the closing transaction.
pub max_fee_satoshis: u64,
}
Normal Channel Operation #
Protocol Diagram
+-------+ +-------+
| |--(1)---- update_add_htlc ---->| |
| |--(2)---- update_add_htlc ---->| |
| |<-(3)---- update_add_htlc -----| |
| | | |
| |--(4)--- commitment_signed --->| |
| A |<-(5)---- revoke_and_ack ------| B |
| | | |
| |<-(6)--- commitment_signed ----| |
| |--(7)---- revoke_and_ack ----->| |
| | | |
| |--(8)--- commitment_signed --->| |
| |<-(9)---- revoke_and_ack ------| |
+-------+ +-------+
TODO(philiphayes): finish this section
def: update_add_htlc
wire message #
/// An update_add_htlc message to be sent or received from a peer
pub struct UpdateAddHTLC {
/// The channel ID
pub channel_id: [u8; 32],
/// The HTLC ID
pub htlc_id: u64,
/// The HTLC value in milli-satoshi
pub amount_msat: u64,
/// The payment hash, the pre-image of which controls HTLC redemption
pub payment_hash: PaymentHash,
/// The expiry height of the HTLC
pub cltv_expiry: u32,
pub(crate) onion_routing_packet: OnionPacket,
}